﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using MineSweeper.EventManagers;
using System.Runtime.InteropServices;
using System.Windows.Interop;

namespace MineSweeper.Behaviors
{
    /// <summary>
    /// The WindowBehavior class contains behaviors that
    /// can be applied to a given window
    /// </summary>
    public static class WindowBehavior
    {
        /// <summary>
        /// The purpose of the HwndSourceHookSizingEventManager is to allow us to 
        /// hok the Window Procedure of a Window in a behavior in a safe way.
        /// Because we're in the context of a behavior we're using the
        /// weak event pattern so we don't cause the window to stay alive.
        /// This event manager will only deliver WM_SIZING messages
        /// </summary>
        private class HwndSourceHookSizingEventManager : WeakEventManager
        {
            private const Int32 WM_SIZING = 0x0214;

            private HwndSourceHookSizingEventManager() { }

            public static void AddListener(HwndSource source, IWeakEventListener listener)
            {
                CurrentManager.ProtectedAddListener(source, listener);
            }

            public static void RemoveListener(HwndSource source, IWeakEventListener listener)
            {
                CurrentManager.ProtectedRemoveListener(source, listener);
            }

            protected override void StartListening(object source)
            {
                ((HwndSource)source).AddHook(hWndSource_WndProc);
            }

            protected override void StopListening(object source)
            {
                ((HwndSource)source).RemoveHook(hWndSource_WndProc);
            }

            private IntPtr hWndSource_WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
            {
                if (msg == WM_SIZING)
                {
                    //Package up the wndProc args and pass them to the weak event listener
                    var eventArgs = new EventArgs<IntPtr, IntPtr>(wParam, lParam);
                    base.DeliverEvent(HwndSource.FromHwnd(hwnd), eventArgs);
                    wParam = eventArgs.Value;
                    lParam = eventArgs.Value2;
                }
                return IntPtr.Zero;
            }

            private static HwndSourceHookSizingEventManager CurrentManager
            {
                get
                {
                    var managerType = typeof(HwndSourceHookSizingEventManager);
                    var currentManager = (HwndSourceHookSizingEventManager)WeakEventManager.GetCurrentManager(managerType);
                    if (currentManager == null)
                    {
                        currentManager = new HwndSourceHookSizingEventManager();
                        WeakEventManager.SetCurrentManager(managerType, currentManager);
                    }
                    return currentManager;
                }
            }
        }

        /// <summary>
        /// This weak event listener is listening to the wndproc of the window we're interested in
        /// </summary>
        private class WindowSizingWeakEventListener : IWeakEventListener
        {
            // Re-written rectangle structure to accomodate our aspect ratio
            /// <summary>A native Rectangle Structure.</summary>
            [StructLayout(LayoutKind.Sequential)]
            private struct Rectangle
            {
                public Rectangle(int left, int top, int right, int bottom)
                {
                    Left = left;
                    Top = top;
                    Right = right;
                    Bottom = bottom;
                }

                public int Left;
                public int Top;
                public int Right;
                public int Bottom;

                public int Width
                {
                    get
                    {
                        return Right - Left;
                    }
                }
                public int Height
                {
                    get
                    {
                        return Bottom - Top;
                    }
                }
            }

            private const Int32 WMSZ_LEFT = 1;
            private const Int32 WMSZ_TOP = 3;
            private const Int32 WMSZ_BOTTOM = 6;
            private const Int32 WMSZ_RIGHT = 2;

            bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
            {
                if (managerType == typeof(HwndSourceHookSizingEventManager))
                {
                    var window = (Window)((HwndSource)sender).RootVisual;
                    var wndProcEventArgs = (EventArgs<IntPtr,IntPtr>)e;
                    var wParam = wndProcEventArgs.Value;
                    var lParam = wndProcEventArgs.Value2;
                    var aspectRatio = (double)window.GetValue(AspectRatioProperty);

                    //This is the rectangle to which the window would resize normally
                    var rc = (Rectangle)
                        Marshal.PtrToStructure(lParam,
                        typeof(Rectangle));

                    //This is the resizing direction
                    int res = wParam.ToInt32();

                    if (res == WMSZ_LEFT || res == WMSZ_RIGHT)
                    {
                        //Adjust height and vertical position
                        int targetHeight = (int)(window.Width / aspectRatio);
                        int originalHeight = rc.Bottom - rc.Top;
                        int diffHeight = originalHeight - targetHeight;

                        rc.Top += (diffHeight / 2);
                        rc.Bottom = rc.Top + targetHeight;
                    }
                    else if (res == WMSZ_TOP || res == WMSZ_BOTTOM)
                    {
                        //Adjust width and horizontal position
                        int targetWidth = (int)(window.Height * aspectRatio);
                        int originalWidth = rc.Right - rc.Left;
                        int diffWidth = originalWidth - targetWidth;

                        rc.Left += (diffWidth / 2);
                        rc.Right = rc.Left + targetWidth;
                    }
                    else if (res == WMSZ_RIGHT + WMSZ_BOTTOM)
                    {
                        //Lower-right corner
                        rc.Bottom = rc.Top + (int)(window.Width / aspectRatio);
                    }
                    else if (res == WMSZ_LEFT + WMSZ_BOTTOM)
                    {
                        //Lower-left corner
                        rc.Bottom = rc.Top + (int)(window.Width / aspectRatio);
                    }
                    else if (res == WMSZ_LEFT + WMSZ_TOP)
                    {
                        //Upper-left corner
                        rc.Left = rc.Right - (int)(window.Height * aspectRatio);
                    }
                    else if (res == WMSZ_RIGHT + WMSZ_TOP)
                    {
                        //Upper-right corner
                        rc.Right = rc.Left + (int)(window.Height * aspectRatio);
                    }

                    window.MinWidth = window.MinHeight * aspectRatio;

                    Marshal.StructureToPtr(rc, lParam, true);
                    return true;
                }
                return false;
            }
        }

        /// <summary>
        /// To implement the AspectRatio we need to listen to the WM_SIZING event of
        /// the window. 
        /// </summary>
        private static readonly WindowSizingWeakEventListener weakEventListener = new WindowSizingWeakEventListener();

        /// <summary>
        /// The AspectRatioProperty attached property exposes an AspectRatio to Window objects
        /// </summary>
        public static readonly DependencyProperty AspectRatioProperty =
        DependencyProperty.RegisterAttached(
        "AspectRatio",
        typeof(double),
        typeof(WindowBehavior),
        new FrameworkPropertyMetadata(-1d,
            FrameworkPropertyMetadataOptions.NotDataBindable,
            (d, e) =>
            {
                var window = d as Window;
                if (window != null)
                {
                    if ((double)e.OldValue < 0)
                    {
                        var hWndSource = (HwndSource)PresentationSource.FromVisual(window);
                        //It's possible the window wont be loaded at this point
                        //If that's the case, hook the loaded event and trap there
                        if (hWndSource == null && !window.IsLoaded)
                        {
                            window.Loaded += new RoutedEventHandler(window_Loaded);
                        }
                        else
                        {
                            HwndSourceHookSizingEventManager.AddListener(hWndSource, weakEventListener);
                        }
                    }
                    window.Width = window.Height * (double)e.NewValue; ;
                    window.InvalidateVisual();
                }
            })
        );

        /// <summary>
        /// Fired when the window loads
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void window_Loaded(object sender, RoutedEventArgs e)
        {
            //Disconnect from this load event and hook up our sizing event manager
            var window = (Window)sender;
            window.Loaded -= new RoutedEventHandler(window_Loaded);

            HwndSourceHookSizingEventManager.AddListener(
                (HwndSource)PresentationSource.FromVisual(window), weakEventListener);
        }

        /// <summary>
        /// Returns the aspect ratio of the dependency object 
        /// </summary>
        /// <param name="obj">The Window we're reading the AspectRatio on</param>
        /// <returns>The AspectRatio</returns>
        public static double GetAspectRatio(DependencyObject obj)
        {
            return (double)obj.GetValue(AspectRatioProperty);
        }

        /// <summary>
        /// Sets the aspectRatio of the dependency object
        /// </summary>
        /// <param name="obj">The Window we're reading the AspectRatio on</param>
        /// <param name="value">The new aspect ratio</param>
        public static void SetAspectRatio(DependencyObject obj, double value)
        {
            obj.SetValue(AspectRatioProperty, value);
        }
    }
}
